package main.userInterface.hexislandui;


import main.model.critters.Critter;
import main.model.critters.CritterCensus;
import main.model.hexisland.HexIsland;
import main.model.hexisland.location.SubHexLocation;
import main.model.hexisland.listener.HexIslandListener;
import main.model.hexisland.location.SubHexLocationPath;
import main.model.hexisland.tile.TileTree;

import javax.swing.*;
import java.awt.*;
import java.awt.geom.Arc2D;
import java.awt.geom.Ellipse2D;
import java.awt.geom.GeneralPath;
import java.awt.geom.RoundRectangle2D;
import java.util.ArrayList;
import java.util.logging.Logger;

/**
 * Created with IntelliJ IDEA.
 * User: gabe
 * Date: 11/06/13
 * Time: 21:30
 */
public class HexIslandCanvas extends JPanel {

    private static final Logger LOG = Logger.getLogger("canvas");


    /*Colour palate*/
    public static final Color WATER = new Color(52, 106, 147);
    public static final Color SAND = new Color(224,194,64);
    public static final Color GRASS = new Color(96, 223, 103);
    public static final Color TREE_GREEN = new Color(0,151,8);
    public static final Color TREE_BROWN = new Color(210,104,0);
    public static final Color ROCK = new Color(140, 123, 72);

    private HexIsland hexIsland;
    private CritterCensus critters;

    //Grid Dimensions//
    private int ZOOM = 30;
    private double sea, beach, seaBeach, obstacleR, critterToken, islandWidth, islandHeight, hexHeight, hexWidth;

    //Display booleans//
    private boolean gridRefs = true; //Grid numbers, on or off
    private boolean displayCritters = true;
    private boolean highQuality = false;

    private double delta = 0;
    private ArrayList<ArrayList<SubHexLocationPath>> accessOptions;

    public HexIslandCanvas(HexIsland hexIsland){
        displayCritters = false;
        setIsland(hexIsland);
        setBackground(WATER);
    }

    public HexIslandCanvas(CritterCensus critters){
        setIsland(critters.getIsland());
        this.critters = critters;
        setBackground(WATER);
    }





    public void paintComponent(Graphics g){
        super.paintComponent(g);
        Graphics2D g2 = (Graphics2D)g;

        setPaintQuality(g2);

        drawTiles(g2);
        //if(isGridRefs())
        if(accessOptions != null) drawAccessAreas( g2, accessOptions);
        if(displayCritters) drawCritters(g2);
        drawObstacles(g2);
    }


    public void setPaintQuality(Graphics2D g2){
        if(highQuality){
            RenderingHints hints = new RenderingHints(
                    RenderingHints.KEY_ANTIALIASING,
                    RenderingHints.VALUE_ANTIALIAS_ON);
            hints.add(new RenderingHints(
                    RenderingHints.KEY_RENDERING,
                    RenderingHints.VALUE_RENDER_QUALITY));
            g2.setRenderingHints(hints);
        }else{
            RenderingHints hints = new RenderingHints(
                    RenderingHints.KEY_ANTIALIASING,
                    RenderingHints.VALUE_ANTIALIAS_OFF);
            hints.add(new RenderingHints(
                    RenderingHints.KEY_RENDERING,
                    RenderingHints.VALUE_RENDER_SPEED));
            g2.setRenderingHints(hints);
        }
    }


    /**
     *
     * @param g2
     */
    private void drawTiles(Graphics2D g2){
        g2.setPaint(SAND);
        g2.fill(new RoundRectangle2D.Double(sea, sea, islandWidth +(beach *2), islandHeight +(beach *2), ZOOM, ZOOM));

        for(int r = 0; r < hexIsland.getRSize(); r++){

            for(int q = 0; q < hexIsland.getQSize(); q++){
                boolean draw = false;

                switch(hexIsland.getTileType(q,r)){
                    case GRASS: g2.setPaint(GRASS); draw = true; break;
                    case TREE: g2.setPaint(GRASS); draw = true; break;
                    case WATER: g2.setPaint(WATER);  draw = true; break;
                }

                if(draw){
                    double centreX = getCentreX(q, r);
                    double centreY = getCentreY(q, r);
                    GeneralPath hexPath = calculateHex(centreX , centreY );
                    g2.fill(hexPath);
                }
            }
        }
    }

    private void drawSubHexBorders(Graphics2D g2){
        for(int r = 0; r < hexIsland.getRSize(); r++){
            for(int q = 0; q < hexIsland.getQSize(); q++){

                double centreX = getCentreX(q, r);
                double centreY = getCentreY(q, r);

                g2.setPaint(Color.red);
                g2.draw(calculateSubHex(centreX, centreY, 0));
                g2.draw(calculateSubHex(centreX,centreY,2));
                g2.draw(calculateSubHex(centreX, centreY, 4));
            }
        }
    }

    /**
     *
     * @param g2
     */
    private void drawObstacles(Graphics2D g2){
        for(int r = 0; r < hexIsland.getRSize(); r++){
            for(int q = 0; q < hexIsland.getQSize(); q++){

                if(hexIsland.hasCentralObstacle(q,r)){
                    double centreX = getCentreX(q, r);
                    double centreY = getCentreY(q, r);

                    switch(hexIsland.getTileType(q,r)){
                        case TREE:
                            g2.setPaint(HexIslandCanvas.TREE_GREEN);
                            g2.fill(new Ellipse2D.Double(centreX - obstacleR /2, centreY - obstacleR /2 , obstacleR, obstacleR));
                            TileTree t = (TileTree) hexIsland.getTile(q, r);
                            double angle = 3.6*(100.0/TileTree.getTreeMax()*t.getTree());
                            g2.setPaint(HexIslandCanvas.TREE_BROWN);
                            g2.fill(new Arc2D.Double(
                                    centreX - obstacleR / 2, centreY - obstacleR / 2, obstacleR, obstacleR,
                                    angle, 360 - angle, Arc2D.PIE));

                            break;
                        case ROCK:
                            g2.setPaint(ROCK);
                            g2.fill(new Ellipse2D.Double(centreX - obstacleR /2, centreY - obstacleR /2 , obstacleR, obstacleR));
                            break;
                    }
                }
            }
        }
    }

    private GeneralPath calculateHex(double centreX, double centreY){
        GeneralPath hexPath = new GeneralPath();

        double angle = 2 * Math.PI / 6 * (0 + 0.5);
        hexPath.moveTo(
                centreX + ZOOM * Math.cos(angle),
                centreY + ZOOM * Math.sin(angle));

        for(int i=1; i<6; i++) {
            angle = 2 * Math.PI / 6 * (i + 0.5);
            hexPath.lineTo(
                    centreX + ZOOM * Math.cos(angle),
                    centreY + ZOOM * Math.sin(angle));
        }
        hexPath.closePath();

        return hexPath;
    }

    /**
     *
     * @param centreX
     * @param centreY
     * @param sector
     * @return
     */
    private GeneralPath calculateSubHex(double centreX, double centreY, int sector){
        GeneralPath hexPath = new GeneralPath();
        hexPath.moveTo(centreX , centreY);

        double angle = (Math.PI / 3 ) * (sector + 2);
        hexPath.lineTo(
                centreX + ZOOM * Math.sin(angle),
                centreY + ZOOM * Math.cos(angle));

        angle = (Math.PI / 3) * (sector +1);
        hexPath.lineTo(
                centreX + ZOOM * Math.sin(angle),
                centreY + ZOOM * Math.cos(angle));

        hexPath.closePath();

        return hexPath;
    }

    private void drawCritters(Graphics2D g2){
        if(critters != null){
            for(Critter critter : critters.getCritters()){
                if(critter.getLocationPath() != null){
                    //drawPath(g2, critter.getLocationPath());

                    g2.setPaint(TREE_BROWN);
                    double index = ( delta * (double)(critter.getLocationPath().size()-1));
                    int aindex = (int)(Math.floor(index));
                    if(aindex <0) aindex = 0;
                    SubHexLocation a = critter.getLocationPath().get(aindex);
                    int bindex = (int)(Math.ceil(index));
                    if(bindex >=  critter.getLocationPath().size()) bindex = critter.getLocationPath().size()-1;
                    if(bindex < 0) bindex = 0;
                    SubHexLocation b = critter.getLocationPath().get(bindex);
                    g2.fill(calculateCritter(calculateSubHexLocationCentre(a, b, index - Math.floor(index))));
                }
                else if  (critter.getLocation() == null) {
                    g2.fill(calculateCritter(calculateSubHexLocationCentre(critter.getLocation())));
                }

                else LOG.warning("Critter " + critter.toString() + " has a null location.");
            }
        }
    }

    private Point calculateSubHexLocationCentre(SubHexLocation locationA , SubHexLocation locationB , double magnitude){
        double centreXA = getCentreX(locationA.getQ(), locationA.getR()) ;
        double centreYA = getCentreY(locationA.getQ(), locationA.getR()) ;

        double angle = (Math.PI / 3) * locationA.getSector() + 1.5;
        centreXA += (ZOOM/2) * Math.sin(angle);
        centreYA += (ZOOM/2) * Math.cos(angle);

        double centreXB = getCentreX(locationB.getQ(), locationB.getR()) ;
        double centreYB = getCentreY(locationB.getQ(), locationB.getR()) ;

        angle = (Math.PI / 3) * locationB.getSector() + 1.5;
        centreXB += (ZOOM/2) * Math.sin(angle);
        centreYB += (ZOOM/2) * Math.cos(angle);


        int x = (int)(centreXA + magnitude * (centreXB - centreXA));
        int y = (int)(centreYA + magnitude * (centreYB - centreYA));

        return new Point(x , y);
    }

    private Point calculateSubHexLocationCentre(SubHexLocation location){
        double centreX = getCentreX(location.getQ(), location.getR()) ;
        double centreY = getCentreY(location.getQ(), location.getR()) ;

        double angle = (Math.PI / 3) * location.getSector() + 1.5;
        centreX += (ZOOM/2) * Math.sin(angle);
        centreY += (ZOOM/2) * Math.cos(angle);


        return new Point((int)centreX , (int)centreY);
    }

    /**
     * Calculates the path to draw a critter triangle.
     */
    private GeneralPath calculateCritter(Point point){
        int x = point.x;
        int y = point.y;

        GeneralPath critterPath = new GeneralPath();

        critterPath.moveTo(
                x + (Math.cos(Math.toRadians(90)) * critterToken),
                y + (Math.sin(Math.toRadians(90)) * critterToken));

        critterPath.lineTo(
                x + (Math.cos(Math.toRadians(210)) * critterToken) ,
                y + (Math.sin(Math.toRadians(210)) * critterToken) );

        critterPath.lineTo(
                x + (Math.cos(Math.toRadians(330)) * critterToken) ,
                y + (Math.sin(Math.toRadians(330)) * critterToken) );

        critterPath.closePath();

        return critterPath;
    }

    /**
     * Returns a slightly darker version of the color provided
     * @param oldColor
     * @return
     */
    private Color darker(Color oldColor) {
        double FACTOR = 0.85;
        return new Color(
                Math.max((int)(oldColor.getRed() * FACTOR), 0),
                Math.max((int)(oldColor.getGreen() * FACTOR), 0),
                Math.max((int)(oldColor.getBlue() * FACTOR), 0));
    }

    /**
     * Iterates over the locations and draws them in darkening colours.
     * @param g2
     * @param accessLocations
     */
    private void drawAccessAreas(Graphics2D g2, ArrayList<ArrayList<SubHexLocationPath>> accessLocations){
        Color stepColor = Color.magenta;
        //int step = 0;
        for(ArrayList<? extends SubHexLocation> subList : accessLocations){
            //LOG.info("Taking step "+step+" with "+subList.size()+" places.");
            //step++;
            for(SubHexLocation location : subList){
                g2.setPaint(stepColor);
                g2.fill(calculateSubHex(
                        getCentreX(location.getQ(), location.getR()),
                        getCentreY(location.getQ(), location.getR()),
                        location.getSector()));
                //LOG.info("Location: "+location.toString());
            }
            stepColor = darker(stepColor) ;
        }
    }

    /**
     * Draws a critters' path as a single line
     * @param g2
     * @param accessLocations
     */
    private void drawPath(Graphics2D g2, ArrayList<SubHexLocation> accessLocations){
        Color stepColor = Color.red;

        g2.setPaint(stepColor);

        GeneralPath path = new GeneralPath();
        Point point = calculateSubHexLocationCentre(accessLocations.get(0));
        path.moveTo(point.getX() , point.getY());
        for(SubHexLocation location : accessLocations){

            point = calculateSubHexLocationCentre(location);
            path.lineTo(point.getX() , point.getY());
        }
        g2.draw(path);
    }


    /**
     * Sets the Zoom and all other parameters based from that figure
     * @param ZOOM
     */
    public void setZoom(double ZOOM){
        this.ZOOM = (int) ZOOM;
        sea = 20; //Sea
        beach = ZOOM/2; //Beach
        seaBeach = (int) sea +(int) beach; //sea and sand size
        obstacleR = ZOOM - (ZOOM/6);//tree size
        critterToken = ZOOM/3; //triangle size
        hexHeight = ZOOM*2;
        hexWidth = Math.sqrt(3)/2 * hexHeight;
        islandWidth = (hexWidth * hexIsland.getQSize()) + (hexWidth /2); // The height of the island
        islandHeight = (hexIsland.getRSize()*(hexHeight *0.75) + (hexHeight /2));  // The width of the island

        this.setPreferredSize(new Dimension((int)((seaBeach *2)+ islandWidth), (int)((seaBeach *2)+ islandHeight)));
        repaint();
    }

    public void setIsland(HexIsland hexIsland){
        this.hexIsland = hexIsland;
        hexIsland.addIslandListener(new HexIslandListener() {
            @Override
            public void onTileChange(int q, int r) {
                setZoom(ZOOM);
            }

            @Override
            public void onIslandChange() {
                setZoom(ZOOM);
            }
        });
        setZoom(ZOOM);
    }

    public void useGridRefs(boolean gridRefs){this.gridRefs=gridRefs; repaint();}
    public boolean isGridRefs(){return gridRefs;}

    public void useHighQuality(boolean highQuality){this.highQuality = highQuality; repaint();}
    public boolean isHighQuality(){return highQuality;}

    /**
     * Returns the X location for the centre of the hexagon with coordinates (Q,R)
     * @param q
     * @param r
     * @return
     */
    private double getCentreX(int q, int r){
        return (seaBeach + hexWidth /2) + (hexWidth *q)  + ((r&1) * hexWidth /2);
    }

    /**
     * Returns the Y location for the centre of the hexagon with coordinates (Q,R)
     * @param q
     * @param r
     * @return
     */
    private double getCentreY(int q, int r){
        return seaBeach + r*(hexHeight *0.75) + (hexHeight /2);
    }

    /**
     * Gives the approximate Q reference of the hexagon which would be at the position X,Y.
     * This method does not account for the complexity of the geometry of a hexagon.
     * @param xMouse    the X location of the mouse
     * @param yMouse    the Y location of the mouse
     * @return  the approximate Q reference
     */
    public int getQbyMouse(int xMouse, int yMouse) {
        return (int)((xMouse-(seaBeach + hexWidth /2)) / hexWidth);
    }

    /**
     * Gives the approximate R reference of the hexagon which would be at the position X,Y
     * This method does not account for the complexity of the geometry of a hexagon.
     * @param xMouse    the X location of the mouse
     * @param yMouse    the Y location of the mouse
     * @return  the approximate R reference
     */
    public int getRbyMouse(int xMouse, int yMouse) {
        return (int)((yMouse-(seaBeach)) / (hexHeight * 0.75));
    }

    public void setDelta(double delta) {
        this.delta = delta;
        repaint();
    }

    public void setAccessOptions(ArrayList<ArrayList<SubHexLocationPath>> accessOptions) {
        this.accessOptions = accessOptions;
        repaint();
    }
}
